/*
The MIT License (MIT)

Copyright (c) 2013 Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "hhdD3D11.h"
#include "hhdGfxPlatform.h"
#include "hhdAssert.h"
#include "hhdGfxDeviceD3D11.h"
#include "hhdGfxViewportD3D11.h"
#include "hhdGfxRenderTargetD3D11.h"

namespace hhd
{
	static ID3D11RenderTargetView*const null_rts[8] = {0,0,0,0,0,0,0,0};
	static ID3D11ShaderResourceView*const null_srvs[8] = {0,0,0,0,0,0,0,0};

	GfxDeviceD3D11::GfxDeviceD3D11()
		: IGfxDevice()
		, _dxgi_factory(NULL)
		, _d3d11_device(NULL)
		, _d3d11_device_context(NULL)
	{
	}

	GfxDeviceD3D11::~GfxDeviceD3D11()
	{
	}

	void GfxDeviceD3D11::initialize()
	{
		HHD_ASSERT( _d3d11_device == NULL );
		HHD_ASSERT( _d3d11_device_context == NULL );

		static UINT creation_flags = 0;

		static D3D_FEATURE_LEVEL feature_levels[] =
		{
			D3D_FEATURE_LEVEL_11_0,
			D3D_FEATURE_LEVEL_10_1,
			D3D_FEATURE_LEVEL_10_0,
			D3D_FEATURE_LEVEL_9_3,
			D3D_FEATURE_LEVEL_9_2,
			D3D_FEATURE_LEVEL_9_1,
		};

		static UINT feature_level_count = ARRAYSIZE( feature_levels );

		// out 
		D3D_FEATURE_LEVEL resulting_feature_level;

		//create the device
		HRESULT hr = D3D11CreateDevice( NULL,
										D3D_DRIVER_TYPE_HARDWARE,
										NULL,
										creation_flags,
										feature_levels,
										feature_level_count,
										D3D11_SDK_VERSION,
										&_d3d11_device,
										&resulting_feature_level,
										&_d3d11_device_context );

		HHD_ASSERT( hr == S_OK );
		//@baw	TEST for now since I'm only dealing with d3d11 support
		HHD_ASSERT( resulting_feature_level == D3D_FEATURE_LEVEL_11_0 );

		//@baw	Now that we have the device, we need to go back and get the Factory that created
		//		it for later needs.
		IDXGIDevice* base_device;
		IDXGIAdapter* adapter;

		hr = _d3d11_device->QueryInterface(__uuidof(IDXGIDevice), (void**)&base_device);
		HHD_ASSERT(hr == S_OK);

		hr = base_device->GetParent(__uuidof(IDXGIAdapter), (void**)&adapter);
		HHD_ASSERT(hr == S_OK);

		hr = adapter->GetParent(__uuidof(IDXGIFactory), (void**)&_dxgi_factory);
		HHD_ASSERT(hr == S_OK);

		//release the temp resources
		adapter->Release();
		base_device->Release();
	}

	void GfxDeviceD3D11::finalize()
	{
		HHD_ASSERT(_dxgi_factory != NULL);
		HHD_ASSERT(_d3d11_device != NULL);
		HHD_ASSERT(_d3d11_device_context != NULL);

		_dxgi_factory->Release();
		_dxgi_factory = NULL;
		_d3d11_device_context->Release();
		_d3d11_device_context = NULL;
		_d3d11_device->Release();
		_d3d11_device = NULL;
	}

	void GfxDeviceD3D11::createViewport(GfxViewport*& out_viewport , const GfxViewportConfig& in_config)
	{
		out_viewport = new GfxViewportD3D11;
		out_viewport->initialize(this, in_config);
	}

	void GfxDeviceD3D11::destroyViewport(GfxViewport*& inout_viewport)
	{
		inout_viewport->finalize();
		delete inout_viewport;
		inout_viewport = NULL;
	}

	void GfxDeviceD3D11::createSwapChain(IDXGISwapChain*& out_swap_chain,
		DXGI_SWAP_CHAIN_DESC& in_swap_chain_description) const
	{
		HRESULT hr = _dxgi_factory->CreateSwapChain(_d3d11_device,
			&in_swap_chain_description,
			&out_swap_chain);

		HHD_ASSERT(hr == S_OK);
	}

	void GfxDeviceD3D11::destroySwapChain( IDXGISwapChain*& inout_swap_chain ) const
	{
		HHD_ASSERT(inout_swap_chain != NULL);
		inout_swap_chain->Release();
		inout_swap_chain = NULL;
	}

	void GfxDeviceD3D11::createRenderTarget(const GfxTexture2dConfig& in_config, GfxRenderTargetD3D11*& out_render_target) const
	{
		//create texture
		D3D11_TEXTURE2D_DESC texture2d_desc;
		texture2d_desc.Width = in_config.width;
		texture2d_desc.Height = in_config.height;
		texture2d_desc.MipLevels = in_config.mip_count;
		texture2d_desc.ArraySize = 1;
		texture2d_desc.Format = Texture2DFormatD3D11(in_config.format);
		texture2d_desc.SampleDesc.Count = 1;
		texture2d_desc.SampleDesc.Quality = 0;
		texture2d_desc.Usage = D3D11_USAGE_DEFAULT;
		texture2d_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
		texture2d_desc.CPUAccessFlags = 0;
		texture2d_desc.MiscFlags = 0;

		ID3D11Texture2D* texture2d;
		HRESULT hr = _d3d11_device->CreateTexture2D(&texture2d_desc, NULL, &texture2d);
		HHD_ASSERT(hr == S_OK);

		createRenderTargetFromTexture(in_config, out_render_target, texture2d);
	}

	void GfxDeviceD3D11::createRenderTargetFromTexture( const GfxTexture2dConfig& in_config, GfxRenderTargetD3D11*& out_render_target, ID3D11Texture2D* in_texture, bool in_is_backbuffer /*= false*/ ) const
	{
		//create render target
		ID3D11RenderTargetView* render_target;
		HRESULT hr = _d3d11_device->CreateRenderTargetView(in_texture, NULL, &render_target);
		HHD_ASSERT(hr == S_OK);

		ID3D11ShaderResourceView* shader_resource = NULL;
		if (!in_is_backbuffer)
		{
			hr = _d3d11_device->CreateShaderResourceView(in_texture, NULL, &shader_resource);
			HHD_ASSERT(hr == S_OK);
		}

		out_render_target = new GfxRenderTargetD3D11(in_config, in_texture, render_target, shader_resource);
	}

	void GfxDeviceD3D11::destroyRenderTarget(GfxRenderTargetD3D11*& inout_render_target ) const
	{
		HHD_ASSERT(inout_render_target != NULL);
		delete inout_render_target;
		inout_render_target = NULL;
	}
	
	void GfxDeviceD3D11::clearRenderTarget(GfxRenderTargetD3D11* in_to_clear, Color in_to_write)
	{
		HHD_ASSERT(_d3d11_device_context != NULL);
		_d3d11_device_context->ClearRenderTargetView(in_to_clear->getRenderTargetView(), in_to_write);
	}

	void GfxDeviceD3D11::applyRenderTargetSetAsDestination( const GfxRenderTargetSet& in_render_target_set )
	{
		HHD_ASSERT(_d3d11_device_context != NULL);
		_d3d11_device_context->OMSetRenderTargets(in_render_target_set.getCount(), in_render_target_set.getRenderTargetViewArray(), NULL);
	}

	void GfxDeviceD3D11::applyRenderTargetSetAsSource( const GfxRenderTargetSet& in_render_target_set )
	{
		HHD_ASSERT(_d3d11_device_context != NULL);
		_d3d11_device_context->PSSetShaderResources(0, in_render_target_set.getCount(), in_render_target_set.getShaderResourceViewArray());
	}

	void GfxDeviceD3D11::clearRenderTargetSetAsDestination( uint32_t in_count )
	{
		HHD_ASSERT(_d3d11_device_context != NULL);
		_d3d11_device_context->OMSetRenderTargets(in_count, null_rts, NULL);
	}

	void GfxDeviceD3D11::clearRenderTargetSetAsSource( uint32_t in_count )
	{
		HHD_ASSERT(_d3d11_device_context != NULL);
		HHD_ASSERT(in_count <= 8);
		_d3d11_device_context->PSSetShaderResources(0, in_count, null_srvs);
	}

}